--[[
quotebot.lua
Implementation of the RASH quote system

-------------------------
Changelog:
0.3
-Can now search with multiple word queries

0.2
-Add quote now supported
-Random quote by search query added
-Added .q ? for help

0.1.1
-.quote now works
-quote request by string now possible

0.1
-Basic functionality;
--View quotes by number
--View latest quote
--View quotes by rating
--Global cooldown timer

To do:
-Quote rating
-Make PPCs faster
-Locally cache the database for performance reasons
]]

--Constants--
bot_name = "QuoteBot"
bot_desc = "The hub quote system"
newline = string.format('\n')
cooldown = 60 --seconds
addcooldown = 300 --seconds
timer = 0
addtimer = 0
-------------

--Regular Expressions--
RE_quote = "^%.qu?o?t?e?"
-----------------------

--Place Lua Code Here--
function Main()
	SendQuery("CREATE TABLE IF NOT EXISTS 'rash_quotes' ('id' int(11) NOT NULL auto_increment, "..
	"'quote' text NOT NULL, "..
	"'rating' int(11) NOT NULL default '0', "..
	"'approve' int(11) NOT NULL default '0', "..
	"'check' int(11) NOT NULL default '0', "..
	"'date' int(11) NOT NULL default '0', "..
	"PRIMARY KEY ('id')) ENGINE=MyISAM AUTO_INCREMENT=1397 DEFAULT CHARSET=latin1;")	
	VH:AddRobot(bot_name, 10, bot_desc, "Bot ", "aethereal@internode.on.net", "0")
end

function UnLoad()
	VH:DelRobot(bot_name)
end

function SendQuery(query)
	local res, err = VH:SQLQuery(query)
	if (not res) then
		SendMessageToHub("An error has occurred!  :( \n"..err.."\nQuery="..query, bot_name)
	end
end

function getQuote(condition)
	ret, rows = VH:SQLQuery("SELECT quote, rating, date FROM 'rash_quotes' "..condition..";")
	if (rows == 0) then
		return nil
	elseif (rows > 1) then
		return nil
		--Whoa this shouldn't happen
	else
		res, quote, rating, date = VH:SQLFetch(0)
		return quote, rating, date
	end
end

--This person wants to do something with a quote
--Possible commands are:
--.q ?
--.q help
--.q a(dd) <quote>
--.q l
--.q <number>
--.q
--.q r
--.q up <number>
--.q down <number>
--.q s(earch) <query>
function VH_OnParsedMsgChat(nick, data)
	if (string.find(data, RE_quote)) then
		if (timer > 0) then
			SendMessageToUser("Quotes can only be requested once every minute.  Please wait "..timer.." seconds.", nick)
			return 0
		end
		local _,_,instruction = string.find(data, RE_quote.."%s(.+)")
		if (instruction == nil) then instruction = "" end
		
		if (instruction == "l") then
			local ret, rows = VH:SQLQuery("SELECT id, quote, rating, date "..
									"FROM rash_quotes WHERE approve = 1 "..
									"ORDER BY id DESC;")
			if (rows == 0) then
				SendMessageToUser("That quote does not exist.", nick)
			else
				local res, id, quote, rating, date = VH:SQLFetch(0)
				local _,len = string.gsub(quote, newline, newline)
				if (len > 6) then
					SendMessageToHub(nick.." generated the latest quote, Quote #"..id..": ("..rating..")"..newline..TooLong(id))
				else
					SendMessageToHub(nick.." generated the latest quote, Quote #"..id..": ("..rating..")"..newline..TagsToText(quote))
				end
				timer = cooldown
			end
		elseif (instruction == "h") then
			local ret, rows = VH:SQLQuery("SELECT id, quote, rating, date "..
									"FROM rash_quotes WHERE approve = 1 "..
									"ORDER BY rating DESC;")
			if (rows == 0) then
				SendMessageToUser("That quote does not exist.", nick)
			else
				local res, id, quote, rating, date = VH:SQLFetch(0)
				local _,len = string.gsub(quote, newline, newline)
				if (len > 6) then
					SendMessageToHub(nick.." generated the highest rated quote #"..id..": ("..rating..")"..newline..TooLong(id))
				else
					SendMessageToHub(nick.." generated the highest rated quote #"..id..": ("..rating..")"..newline..TagsToText(quote))
				end
				timer = cooldown
			end
		elseif (string.find(instruction, "%d$")) then
			local requested = instruction
			if (requested == nil) then
				SendMessageToHub("LOL THE QUOTEBOT IS BROKEN.")
			else
				local ret, rows = VH:SQLQuery("SELECT id, quote, rating, date "..
										"FROM rash_quotes "..
										"WHERE approve = 1 AND id = "..requested..";")
				if (rows == 0) then
					SendMessageToUser("That quote does not exist.", nick)
					--SendMessageToUser("I may be wrong, but this is the quote you were looking for:"..newline..requested, nick)
				else
					local res, id, quote, rating, date = VH:SQLFetch(0)
					local _,len = string.gsub(quote, newline, newline)
					if (len > 6) then
						SendMessageToHub(nick.." requested quote #"..requested..": ("..rating..")"..newline..TooLong(id))
					else
						SendMessageToHub(nick.." requested quote #"..requested..": ("..rating..")"..newline..TagsToText(quote))
					end
					timer = cooldown
				end
			end
		elseif (instruction == "r") or (instruction == "") then
			local ret, rows = VH:SQLQuery("SELECT id, quote, rating, date "..
									"FROM rash_quotes WHERE approve = 1 "..
									"ORDER BY id ASC;")
			if (rows == 0) then
				SendMessageToUser("The quote database is empty!  You need to add quotes first.", nick)
			else
				local rnd = math.random(0,rows)
				local res, id, quote, rating, date = VH:SQLFetch(rnd)
				local _,len = string.gsub(quote, newline, newline)
				if (len > 6) then
					SendMessageToHub(nick.." randomly generated Quote #"..id..": ("..rating..")"..newline..TooLong(id))
				else
					SendMessageToHub(nick.." randomly generated Quote #"..id..": ("..rating..")"..newline..TagsToText(quote))
					timer = cooldown
				end
			end
		elseif (string.find(instruction, "^ad?d?")) then
			local _,_,args = string.find(instruction, "^ad?d?%s(.+)")
			if (args == nil) then
				SendMessageToUser("You can't add an empty quote.", nick)
			else
				--SendMessageToUser("The current os.time() is: "..os.time()..newline.."You are trying to add the following quote:"..newline..args, nick)
				--Operators can make quotes whenever they want.
				--Normal people are globally limited to 5 minutes.
				if (UserClass(nick) < 2) and (addtimer > 0) then --Normal user during cooldown
					SendMessageToUser("Quotes can only be added to the database once every 5 minutes.  Please wait "..addtimer.." seconds.", nick)
					return 0
				end
				
				if (SendQuery("INSERT INTO rash_quotes ( `id` , `quote` , `rating` , `approve` , `check` , `date` ) VALUES ( NULL, '"..TextToTags(args).."', '0', '1', '1', '"..os.time().."' );")) then
					SendMessageToUser("Quote added successfully.", nick)
					--We should find out what id MySQL gave it
					SendQuery("SELECT id FROM rash_quotes WHERE approve = 1 ORDER BY date DESC;")
					local res, id = VH:SQLFetch(0)
					SendMessageToHub("Quote #"..id.." added by "..nick..".\nhttp://voidware.ath.cx/quotes/?"..id)
					if (UserClass(nick) < 2) then addtimer = addcooldown end
				end
			end
		elseif (string.find(instruction, "^s")) then
			local _,_,args = string.find(instruction, "^s%s(.+)")
			if (args == nil) then args = "" end
			
			args = removeAllSymbols(args)
			if (getNumberOfWords(args) > 5) then
				SendMessageToUser("Your query is too long.  It should be five words or less.", nick)
				return 0
			end
			
			local ret, rows = VH:SQLQuery("SELECT id, quote, rating, date "..
										  "FROM rash_quotes WHERE approve = 1 "..
										  "AND quote LIKE '%"..args.."%' "..
										  "ORDER BY id ASC;")
			if (rows == 0) then
				SendMessageToUser("No quotes matched the search query: "..args..".", nick)
			else
				local rnd = math.random(0,rows-1)
				local res, id, quote, rating, date = VH:SQLFetch(rnd)
				local _,len = string.gsub(quote, newline, newline)
				if (len > 6) then
					SendMessageToHub(nick.." generated a quote based on "..'"'..args..'"'.." which got "..rows..pluralise(" result", rows)..", Quote #"..id..": ("..rating..")"..newline..TooLong(id))
				else
					SendMessageToHub(nick.." generated a quote based on "..'"'..args..'"'.." which got "..rows..pluralise(" result", rows)..", Quote #"..id..": ("..rating..")"..newline..TagsToText(quote))
					timer = cooldown
				end
			end
		elseif (instruction == "?") or (instruction == "help") then
			showHelp(nick)
		elseif (instruction == "up") or (instruction == "down") then
			SendMessageToUser("This function is not implemented yet.", nick)
		else
			showHelp(nick)
		end
		return 0
	end
	return 1
end

function showHelp(nick)
	SendMessageToUser("Usage:", nick)
	DisplayHelpMessage("q", "?","Displays this help message.", nick)
	DisplayHelpMessage("q", "a <message>","Adds a quote to the database.  Can be used once every five minutes.", nick)
	DisplayHelpMessage("q", "l","Requests the latest quote and displays it in public chat.", nick)
	DisplayHelpMessage("q", "r","Requests a random quote and displays it in public chat.", nick)
	DisplayHelpMessage("q", "up <number>","Increases a quote's rating by one.", nick)
	DisplayHelpMessage("q", "down <message>","Decreases a quote's rating by one.", nick)
	DisplayHelpMessage("q", "s <query>","Requests a random quote based on a query and displays it in public chat.", nick)
	DisplayHelpMessage("q", "<number>","Requests a specific quote and displays it in public chat.", nick)
	DisplayHelpMessage("q", "","Requests a random quote and displays it in public chat.", nick)
end

function TagsToText(s)
	s = string.gsub(s, "&quot;", '"')
	s = string.gsub(s, "&gt;", ">")
	s = string.gsub(s, "&lt;", "<")
	s = string.gsub(s, "&#92;", "\\")
	s = string.gsub(s, "&#47;", "/")
	return s
end

function TextToTags(s)
	s = string.gsub(s, "'", "''")
	s = string.gsub(s, '"', "&quot;")
	s = string.gsub(s, ">", "&gt;")
	s = string.gsub(s, "<", "&lt;")
	s = string.gsub(s, "\\", "&#92;")
	s = string.gsub(s, "/", "&#47;")
	return s
end

function TooLong(n)
	return "Quote is too long to display in main chat ... http://voidware.ath.cx/quotes/?"..n
end

function VH_OnTimer()
	if (timer > 0) then
		timer = timer - 1
	end
	
	if (addtimer > 0) then
		addtimer = addtimer - 1
	end
end

function SendQuery(query)
	local res, err = VH:SQLQuery(query)
	if (not res) then
		SendMessageToHub("An error has occurred!  :( \n"..err.."\nQuery="..query, bot_name)
	end
	return res, err
end
-----------------------

--Common Functions--
function SendMessageToHub(data, from)
	if (from == nil) then
		--Send it from hub security
		from = bot_name
	end
	return VH:SendDataToAll("<"..from.."> "..data.."|",0,10)
end

function SendMessageToUser(data, nick, from)
	if (from == nil) then
		--Send it from hub security.
		from = bot_name
	end
	
	if (nick == nil) then
		--Send it to the whole hub.
		return VH:SendDataToAll("<"..from.."> "..data.."|",0,10)
	end
	
	return VH:SendDataToUser("<"..from.."> "..data.."|", nick)
end

function SendPrivateMessageToUser(user, data, from)
    return VH:SendDataToUser("$To: "..user.." From: "..from.." $<"..from.."> "..data.."|", user);
end

function DisplayHelpMessage(cmdname, cmd, msg, nick)
    SendMessageToUser("        ."..cmdname.." "..cmd, nick, bot_name)
    SendMessageToUser("        "..msg, nick, bot_name)
end

function UserClass(nick)
	res, class = VH:GetUserClass(nick)
	if res and class then return class else return 0 end
end

function UserIsOnline(nick)
	result, sIP = VH:GetUserIP(nick)
	if (string.len(sIP) < 7) then
		return false
	else
		return true
	end
end

function UserIP(nick)
	result, sIP = VH:GetUserIP(nick)
	if (string.len(sIP) < 7) then
		return 0
	else
		return sIP
	end
end

function SendEmoteToHub(data, from)
	result, err = VH:SendDataToAll("<"..from.."> ".."/me "..data.."|",0,10)
	return 1
end

function KickAllUsersWithIP(ip, reason)
	_,nicklist = VH:GetNickList()
	nicklist = string.sub(nicklist, 11)
	flag = false
	for names in string.gmatch(nicklist, "([^%$]+)%$%$") do
		if (ip == UserIP(names)) then
			if (flag == false) then
				VH:KickUser(security, names, reason)
				flag = true
			else
				VH:CloseConnection(names)
			end
		end
	end
end

function getNumberOfWords(s)
	local wc = 0
	for _ in string.gmatch(s, "%a+") do
		wc = wc + 1
	end
	return wc
end

function removeAllSymbols(s)
	local outstring = ""
	for l in string.gmatch(s, "[%w%s]") do
		outstring = outstring..l
	end
	return outstring
end

function pluralise(s, i)
	if (i ~= 1) then
		if (string.sub(s, string.len(s)) == "s") then
			return s.."es"
		else
			return s.."s"
		end
	else
		return s
	end
end
